﻿package de.gameduell.framework.facebook.app {
	import de.gameduell.framework.facebook.app.pyramide.Pyramid;

	import de.gameduell.facebook.service.gen.CatalogItem;
	import de.gameduell.facebook.service.gen.GameState;
	import de.gameduell.framework.debug.Debug;
	import de.gameduell.framework.facebook.FBBaseGameState;
	import de.gameduell.framework.facebook.communication.FacebookData;
	import de.gameduell.framework.facebook.event.DonationEvent;
	import de.gameduell.framework.facebook.event.InventoryEvent;
	import de.gameduell.framework.facebook.reporting.FBBaseTracker;
	import de.gameduell.framework.game.watch.StopWatch;
	import de.gameduell.framework.locale.LocaleData;
	import de.gameduell.framework.util.FlashVars;

	import com.adobe.serialization.json.JSON;

	import flash.events.Event;
	import flash.events.EventDispatcher;

	public class GameData extends EventDispatcher {
		
		public static const KEY_COINS:String = "coins";
		public static const KEY_LIVES:String = "lives";
		public static const KEY_PYRAMID:String = "pyramid";
		public static const KEY_LIVES_TIMESTAMP:String = "lives_timestamp";
		public static const KEY_LIFE_COST:String = "life_cost";
		public static const KEY_BRICK_INVENTORY:String = "brick_inventory";
		public static const KEY_TEMPLE_LEVEL:String = "temple_level";
		
		public static const APP_INIT_DONE:String     = "APP_INIT_DONE";
		public static const APP_LIFE_COST_FOUND:String = "APP_LIFE_COST_FOUND";
		public static const APP_LIVES_CHANGED:String = "APP_LIVES_CHANGED";
		public static const APP_COINS_CHANGED:String = "APP_COINS_CHANGED";
		public static const APP_PYRAMID_CHANGED:String = "APP_PYRAMID_CHANGED";
		public static const APP_LIVES_TIMESTAMP_CHANGED:String = "APP_LIVES_TIMESTAMP_CHANGED";
		public static const APP_MAX_LIVES_CHANGED:String = "MAX_LIVES_CHANGED";
		public static const APP_CLEAN_UP_DONATIONS:String = "APP_CLEAN_UP_DONATIONS";
		public static const APP_BRICK_INVENTORY_CHANGED:String = "APP_BRICK_INVENTORY_CHANGED";
		public static const APP_THANKS_FOR_DONATION:String = "APP_THANKS_FOR_DONATION";		
		

		private static const LIFE_PER_TICK:int = 1;
		
		private var live_recharge_delay:int;
		
		private var _pyramid:Pyramid;
		private var _liveCost:int = 0;
		private var _lives:int = -1;
		private var _coins:int = -1;
		private var _livesTimestamp:Number;
		private var _maxLives:int = 2;
		private var lifeRechargeWatch:StopWatch;
		private var gameState:FBBaseGameState;
		private var localeData:LocaleData;
		private var controller:Controller;
		private var tracker:FBBaseTracker;
		
		private var _donated_lives:int = 0;
		private var _donationStates:Array;
		private var _passedTicks:int = 0;
		private var _oldLives:int;
		
		private var _brickInventory:Array;
		private var _donationReceiver:String;


		public function GameData(aGameState:FBBaseGameState, aController:Controller) {
			_brickInventory = new Array();
			_donationStates = new Array();
			controller = aController;
			controller.facebookData.addEventListener(FacebookData.FBE_FRIEND_DATA, checkForDonationReceiver);
			localeData = LocaleData.getInstance();
			tracker = FBBaseTracker.getInstance();
			_pyramid = new Pyramid();
			_pyramid.addEventListener(Pyramid.NEW_CONSTRUCTION, onPyramidChanged);
			_pyramid.addEventListener(Pyramid.OUT_OF_BRICKS, onOutOfBricks);

			lifeRechargeWatch = new StopWatch();
			lifeRechargeWatch.addEventListener(StopWatch.TIMER_TICK, lifeTimerTick);
			lifeRechargeWatch.addEventListener(StopWatch.TIME_OVER, rechargeLife);
			
			gameState = aGameState;
			gameState.addEventListener(FBBaseGameState.ALL_STATES_CHANGED, initGameData);
			gameState.addEventListener(FBBaseGameState.FIRST_TIME_START, initFirstTimeGameData);
		}
		
		public function onInventoryItemAdded(event:InventoryEvent):void {
			var addedItem:CatalogItem = controller.inventory.getItemById(event.itemId);
			if(addedItem != null){
				Debug.trace("GameData::onInventoryItemAdded: addedItem: " + addedItem.title, Debug.DEBUG);
				var isBrick:Boolean = addedItem.title.split("_")[0] == "brick";

				switch(true) {
					case addedItem.title == "MPY_LIFE":
						lives += 1;
						break;
						
					case isBrick:
						Debug.trace("GameData::onInventoryItemAdded: adding brick to brick inventory: " +  addedItem.title);
						_brickInventory.push(addedItem.title);
						dispatchEvent(new Event(APP_BRICK_INVENTORY_CHANGED));
						break;
				}
			}
		}
		
		public function get donationReceiver():String {
			return _donationReceiver;
		}
		
		public function get maxLives():int {
			return _maxLives;
		}

		private function initFirstTimeGameData(event:Event):void {
			tracker.track("new_user");
			live_recharge_delay = SplittestConfig.getInstance().lifeTimerTick;
			
			gameState.removeEventListener(FBBaseGameState.FIRST_TIME_START, initFirstTimeGameData);

			var brickInventoryState:GameState = new GameState();
			brickInventoryState.key = KEY_BRICK_INVENTORY;
			brickInventoryState.value = "";

			var coinState:GameState = new GameState();
			coinState.key = KEY_COINS;
			coinState.value = "0";

			var lifeCostState:GameState = new GameState();
			lifeCostState.key = KEY_LIFE_COST;
			lifeCostState.value = "0";

			var livesState:GameState = new GameState();
			livesState.key = KEY_LIVES;
			livesState.value = String(_maxLives);

			var livesTimeStampState:GameState = new GameState();
			livesTimeStampState.key = KEY_LIVES_TIMESTAMP;
			livesTimeStampState.value = (new Date()).getTime().toString();

			var pyramidFirstStartState:GameState = new GameState();
			pyramidFirstStartState.key = KEY_PYRAMID;
			pyramidFirstStartState.value = pyramid.pyramidToString();

			gameState.storeState(brickInventoryState);
			gameState.storeState(coinState);
			gameState.storeState(lifeCostState);
			gameState.storeState(livesState);
			gameState.storeState(livesTimeStampState);
			gameState.storeState(pyramidFirstStartState);
						
			lives = int(gameState.readState(KEY_LIVES).value);
			coins = int(gameState.readState(KEY_COINS).value);
			livesTimestamp = Number(gameState.readState(KEY_LIVES_TIMESTAMP).value);
			
			initGameData();
			onPyramidChanged();
		}
		
		private function onPyramidChanged(event:Event = null):void {
			dispatchEvent(new Event(APP_PYRAMID_CHANGED));
			maxLives = pyramid.currentBrick.maxLives;
		}
		
		private function onTempleChanged(event:Event = null):void {
			dispatchEvent(new Event(APP_PYRAMID_CHANGED));
			maxLives = pyramid.currentBrick.maxLives;
		}
		
		private function onOutOfBricks(event:Event):void {
			dispatchEvent(new Event(APP_PYRAMID_CHANGED));
		}
		
		public function set maxLives(newLifeCount:int):void {
			if(newLifeCount > _maxLives){
				Debug.trace("GameData::maxLives: new count! : " + newLifeCount);
				_maxLives = newLifeCount;
				updateLifeCountString();
				dispatchEvent(new Event(APP_MAX_LIVES_CHANGED));
				startLifeRechargeWatch();
			}
		}
		
		private function fixCorruptData(corruptKey:String):void {
			Debug.trace("GameData::fixCorruptData: corruptKey -> " + corruptKey, Debug.WARNING);
			tracker.track("debug.fixCorruptData." + corruptKey);
			var gamestate:GameState = new GameState();
			switch(corruptKey){
				case KEY_LIVES_TIMESTAMP:		
					gamestate.key = KEY_LIVES_TIMESTAMP;
					gamestate.value = (new Date()).getTime().toString();
					gameState.storeState(gamestate);
					break;
					
				case KEY_LIVES:
					gamestate.key = KEY_LIVES;
					gamestate.value = String(_maxLives);
					gameState.storeState(gamestate);
					break;
					
				case KEY_COINS:
					gamestate.key = KEY_COINS;
					gamestate.value = "0";
					gameState.storeState(gamestate);
					break;

				case KEY_PYRAMID:
					gamestate.key = KEY_PYRAMID;
					gamestate.value = pyramid.pyramidToString();
					gameState.storeState(gamestate);
					onPyramidChanged();
					break;
					
				case KEY_TEMPLE_LEVEL:
					gamestate.key = KEY_TEMPLE_LEVEL;
					gamestate.value = "0";
					gameState.storeState(gamestate);
					onTempleChanged();
					break;
			}
			Debug.trace("GameData::fixCorruptData: fixing done ... ", Debug.WARNING);
		}
		
		private function initGameData(event:Event = null):void {
			live_recharge_delay = SplittestConfig.getInstance().lifeTimerTick;
			gameState.removeEventListener(FBBaseGameState.ALL_STATES_CHANGED, initGameData);

			// restoring bought bricks
			try{
				_brickInventory = String(gameState.readState(KEY_BRICK_INVENTORY).value).split(",");
				Debug.trace("GameData::initGameData: restored bought bricks from server: " + _brickInventory, Debug.CONNECTION);
			}catch(e:Error){
				_brickInventory = new Array();
			}

			// restoring life cost - avoid user refresh of browser to keep his life
			try{
				_liveCost = int(gameState.readState(KEY_LIFE_COST).value);
				Debug.trace("GameData::initGameData: restored life_cost from server: " + _liveCost, Debug.CONNECTION);
			}catch(e:Error){
				_liveCost = 0;
			}

			// restoring time stamp
			try{
				_livesTimestamp = Number(gameState.readState(KEY_LIVES_TIMESTAMP).value);
				Debug.trace("GameData::initGameData: restored livesTimestamp from server: " + _livesTimestamp, Debug.CONNECTION);
			}catch(e:Error){
				fixCorruptData(KEY_LIVES_TIMESTAMP);
				_livesTimestamp = Number(gameState.readState(KEY_LIVES_TIMESTAMP).value);
			}
			// restoring lives
			try{
				_lives = int(gameState.readState(KEY_LIVES).value);
				Debug.trace("GameData::initGameData: restored lives from server: " + _lives, Debug.CONNECTION);
			}catch(e:Error){
				fixCorruptData(KEY_LIVES);
				_lives = int(gameState.readState(KEY_LIVES).value);
			}
			// restoring coins
			try{
				_coins = int(gameState.readState(KEY_COINS).value);
				Debug.trace("GameData::initGameData: restored coins from server: " + _coins, Debug.CONNECTION);
			}catch(e:Error){
				fixCorruptData(KEY_COINS);
				_coins = int(gameState.readState(KEY_COINS).value);
			}
			// restoring pyramid
			try{
				var pyramidString:String = String(gameState.readState(KEY_PYRAMID).value);
				Debug.trace("GameData::initGameData: restored pyramid from server: " + pyramidString, Debug.CONNECTION);
			}catch(e:Error){
				fixCorruptData(KEY_PYRAMID);
			}
			
			coins = int(gameState.readState(KEY_COINS).value);
			pyramid.stringToPyramid(String(gameState.readState(KEY_PYRAMID).value));
			_maxLives = pyramid.currentBrick.maxLives;
			Debug.trace("GameData::initGameData:  -> _lives: " + _lives + " _maxLives: " + _maxLives);
			
			_oldLives = _lives;
			
			if(_lives < _maxLives){
				Debug.trace("GameData::initGameData:  -> processPassedTime");
				processPassedTime();
			}else{
				Debug.trace("GameData::initGameData:  -> lives = _lives");
				lives = _lives;
			}

			// ### LIVES COST ###
			if(_liveCost >= 1){
				Debug.trace("GameData::initGameData: found life cost value_lives: " + (_oldLives + _passedTicks)  + "/" + _maxLives, Debug.WARNING);
				_liveCost = 0;
				if((_oldLives + _passedTicks) <= _maxLives){
					if(_passedTicks < _maxLives){
						Debug.trace("GameData::initGameData: removing life cost .., ", Debug.DEBUG);
						lives = lives - 1; // can also be higher?
					}
				}
				dispatchEvent(new Event(APP_LIFE_COST_FOUND));
			}
			checkForDonations();
			
			dispatchEvent(new Event(APP_INIT_DONE));
			
			if(_donated_lives){
				Debug.trace("GameData::initGameData: LIFE DONATION FOUND !!!!!");
				dispatchEvent(new DonationEvent(DonationEvent.DONATION_FROM_BUDDIES, KEY_LIVES, String(_donated_lives)));
				lives = lives + _donated_lives;
			}
			
			if(_donationStates.length > 0){
				dispatchEvent(new Event(APP_CLEAN_UP_DONATIONS));
			}
		}

		public function checkForDonationReceiver(event:Event = null):void {
			_donationReceiver = FlashVars.getKeyValue("donationReceiver") == FlashVars.NO_VALUE ? "none" : FlashVars.getKeyValue("donationReceiver");
			if(_donationReceiver != "none"){
				dispatchEvent(new Event(APP_THANKS_FOR_DONATION));
			}
		}
		
		public function get donationStates():Array {
			return _donationStates;
		}
		
		public function get brickInventory():Array {
			return _brickInventory;
		}
		
		public function userOwnsBrick(aBrickType:String):Boolean {
			for(var i:int = 0; i < _brickInventory.length; i++){
				if(_brickInventory[i] == aBrickType){
					return true;
				}
			}
			return false;
		}
		
		private function checkForDonations():void {
			Debug.trace("GameData::checkForDonations");
			for(var i:* in gameState.states){
				if(GameState(gameState.states[i]).key.split("_donation_").length > 1){
					handleDonation(GameState(gameState.states[i]));
				}
			}
			Debug.trace("GameData::checkForDonations: " + _donated_lives, Debug.CONNECTION);
		}

		private function handleDonation(aGameState:GameState):void {
			Debug.trace("GameData::handleDonation: key: " + aGameState.key +  "       value: " + aGameState.value, Debug.WARNING);
			var donationType:String =  aGameState.key.split("_donation_")[0];
			var donationValue:Object = JSON.decode(aGameState.value);
			
			if(donationValue != null){
				Debug.trace("GameData::handleDonation: VALUE: " + donationValue.value + "   from: "  + donationValue.donator);
				
				switch(donationType){
					case KEY_LIVES:
						if((_lives + _donated_lives) < (2 * _maxLives)){
							_donated_lives = _donated_lives + int(donationValue.value);
						}
						_donationStates.push(aGameState.key);
						break;
				}
			}else{
				Debug.trace("GameData::handleDonation: WRONG DONATION VALUE", Debug.WARNING);
			}
		}

		private function processPassedTime():void {
			Debug.trace("GameData::processPassedTime: _lives -> " + _lives);
			var currTimeStamp:Number = (new Date()).getTime();
			var passedTime:Number = currTimeStamp - _livesTimestamp;
			var passedTicks:int = Math.min(passedTime / live_recharge_delay);
			var timeToTick:Number = live_recharge_delay - (passedTime - passedTicks * live_recharge_delay);
			Debug.trace("GameData::processPassedTime: currTimeStamp -> " + currTimeStamp + " passedTime: " + passedTime + " passedTicks: " + passedTicks);
			if((_lives + passedTicks) < _maxLives){
				lifeRechargeWatch.endTime = timeToTick;
				lifeRechargeWatch.start();
				localeData.setValue("recharging", "true");
			}
			_lives = (_lives + passedTicks) > _maxLives ? _maxLives : (_lives + passedTicks);
			lives = _lives; // just for the update
			
			_passedTicks = passedTicks;
		}

		public function get pyramid():Pyramid {
			return _pyramid;
		}
		
		public function set lives(lifeCount:int):void {
			Debug.trace("GameData:: set lives: " + lifeCount);
			var disEvn:Boolean = _lives != lifeCount;
			if(lifeCount < _lives && _lives == _maxLives) {
				livesTimestamp = (new Date()).getTime();
			}
			_lives = lifeCount >= 0 ? lifeCount : 0;
			updateLifeCountString();
			if(_lives < _maxLives){
				startLifeRechargeWatch();
			}else{
				stopLifeRechargeWatch();
				localeData.setValue("lifetimer", "inactive");
			}
			if(disEvn){
				dispatchEvent(new Event(APP_LIVES_CHANGED));
			}
		}
		
		private function updateLifeCountString():void {
			var lifeCountString:String = String(_lives) + "/" + String(_maxLives);
			localeData.setValue("lifeCounter", lifeCountString);
		}

		private function startLifeRechargeWatch():void {
			if(!lifeRechargeWatch.isRunning){
				Debug.trace("GameData::startLifeRechargeWatch");
				localeData.setValue("recharging", "true");
				lifeRechargeWatch.stop();
				lifeRechargeWatch.endTime = live_recharge_delay;
				lifeRechargeWatch.start();
			}else{
				Debug.trace("startLifeRechargeWatch is already running ... current endtime: " + lifeRechargeWatch.endTime, Debug.DEBUG);
			}
		}
		
		private function stopLifeRechargeWatch():void {
			Debug.trace("GameData::startLifeRechargeWatch");
			localeData.setValue("recharging", "default");
			lifeRechargeWatch.stop();
		}
		
		private function rechargeLife(event:Event):void {
			lives = lives + LIFE_PER_TICK;
//			_lives = lives + LIFE_PER_TICK;
//			lives = _lives; // just for an update ...
			if(lives < _maxLives){
				livesTimestamp = (new Date()).getTime();
				startLifeRechargeWatch(); // write new timestamp to DB
			}else{
				stopLifeRechargeWatch();
			}
		}
		
		private function lifeTimerTick(event:Event):void {
			localeData.setValue("lifeRechargeTick", String(lifeRechargeWatch.remainingFullTimeDigital));
		}

		public function get lives():int {
			return _lives;
		}

		public function set livesTimestamp(aTimeStamp:Number):void {
			var disEvn:Boolean = _livesTimestamp != aTimeStamp;
			_livesTimestamp = aTimeStamp;
			if(disEvn){
				dispatchEvent(new Event(APP_LIVES_TIMESTAMP_CHANGED));
			}
		}
		
		public function get livesTimestamp():Number {
			return _livesTimestamp;
		}
		
		public function set coins(coinCount:int):void {
			var disEvn:Boolean = _coins != coinCount;
			_coins = coinCount;
			if(disEvn){
				dispatchEvent(new Event(APP_COINS_CHANGED));
			}
		}

		public function get coins():int {
			return _coins;
		}
	}
}
